﻿#coding: utf-8

from PointFunctions import PointFunctions
from Constants import *

import pygame
import math

class History():

	def __init__(self):
		self.history_index = 0
		self.max_history = 10
		self.history = [0]*self.max_history

	def get_history(self,rel_index):
		index = (self.history_index + rel_index) % self.max_history
		return self.history[index]
		
	def go_back(self,rel_index):
		index = (self.history_index + rel_index) % self.max_history
		self.history_index = index
		return self.history[index]
		
	def add_to_history(self,xk):
		self.history[self.history_index] = xk
		self.history_index +=1
		self.history_index = self.history_index%self.max_history	

class NullPoint():
	
	def __init__(self):
		self.x = 0
		self.y = 0
	
	def distance(self,p2):
		return pow( ( pow( (p2.x - self.x),2 )  + pow( (p2.y-self.y),2 ) ) ,0.5)	
		
	def normal(self):
		return math.sqrt( math.pow(self.x,2) + math.pow(self.y,2) )
		
	def dot_product(self,p2):
		return self.x*p2.x + self.y*p2.y	

class Point():
	tolerance = 0.082353 #21(255)
	
	def __str__(self):
		return "(" + str(self.x) + "," + str(self.y) + ")"
	
	def __init__(self,x,y,index):
		self.x = x
		self.y = y
		
		self.index = index
		self.color = Colors.RED
		
		self.alfa = 0
		self.beta = 0
		self.gama = (0,0)
		self.delta = 0
		
		self.manual_alfa = None
		self.manual_beta = None
		self.manual_gama = None
		self.manual_delta = None
		
		self.sobre_contorno = False
		self.sobre_contorno_borda = False	
		self.ponto_intermediario = False		
		self.quina = False
		
		self.closest_fixed = INF
		self.closest_characteristic = INF
		self.became_fixed_point = False
		
		self.dir_pt_linear = NullPoint()
		self.var_cor = 0
		self.check_vector = NullPoint()
		
		self.comp_continuidade = (0,0)
		self.comp_suavidade = (0,0)
		self.comp_cor = (0,0)

		self.grad_comp_continuidade = (0,0)
		self.grad_comp_suavidade = (0,0)
		self.grad_comp_cor = (0,0)		
		

	def copy(self):
		_copy = Point(self.x,self.y,self.index)
		
		_copy.color = self.color
		
		_copy.alfa = self.alfa
		_copy.beta = self.beta
		_copy.gama = self.gama
		_copy.delta = self.delta
		
		_copy.manual_alfa = self.manual_alfa
		_copy.manual_beta = self.manual_beta
		_copy.manual_gama = self.manual_gama
		_copy.manual_delta = self.manual_delta		
		
		_copy.sobre_contorno = self.sobre_contorno
		_copy.sobre_contorno_borda = self.sobre_contorno_borda
		_copy.ponto_intermediario = self.ponto_intermediario
		_copy.quina = self.quina
		
		_copy.closest_fixed = self.closest_fixed
		_copy.closest_characteristic = self.closest_characteristic
		_copy.became_fixed_point = self.became_fixed_point

		_copy.var_cor = self.var_cor
		_copy.check_vector = self.check_vector
		_copy.dir_pt_linear = self.dir_pt_linear
		
		_copy.comp_continuidade = self.comp_continuidade
		_copy.comp_suavidade = self.comp_suavidade
		_copy.comp_cor = self.comp_cor

		_copy.grad_comp_continuidade = self.grad_comp_continuidade
		_copy.grad_comp_suavidade = self.grad_comp_suavidade
		_copy.grad_comp_cor = self.grad_comp_cor			
		
		return _copy
		
	def distance(self,p2):
		return pow( ( pow( (p2.x - self.x),2 )  + pow( (p2.y-self.y),2 ) ) ,0.5)	
		
	def normal(self):
		return math.sqrt( math.pow(self.x,2) + math.pow(self.y,2) )
		
	def dot_product(self,p2):
		return self.x*p2.x + self.y*p2.y
		
	def update_1(self,point_vector,color_fn):
		self.ponto_intermediario = False
		
		is_fixed_point_now = self.sobre_contorno
		
		self.sobre_contorno = PointFunctions.calcular_sobre_contorno(self,color_fn,Point.tolerance)
		
		if is_fixed_point_now == False and self.sobre_contorno == True:
			self.became_fixed_point = True		
		else:
			self.became_fixed_point = False
		
		self.var_cor = PointFunctions.calcular_variacao_cor_normalizada(self,color_fn,Point.tolerance)							

				
	def update_2(self,point_vector,color_fn):
		#Existem atualiza��es que dependem de componentes j� atualizadas, como � o caso do ponto vizinho. Para saber se um ponto � intermedi�rio eu preciso saber se seus vizinhos est�o sobre o contorno				
		
		self.set_as_characteristic(color_fn)
		self.set_as_border(point_vector)
		self.set_as_neighbors(point_vector)
		
		#Neste momento, o dir_pt_linear sempre � o dir_pt_medio (pelo menos at� que se tenha alcan�ado 50% de pontos fixos
		#self.check_vector = self.dir_pt_linear	

	
		'''
		#Setting dir_pt_linear
		if self.closest_fixed < 6:
			fixo_relativo = self.index + self.closest_fixed
			fixo_relativo = point_vector.points[fixo_relativo]						
			
			#O vetor de check � usado para decidir em qual dire��o seguir no direcionamento linear. A princ�pio, o vetor
			#tomado como check � o que direciona para o pt_medio, mas depois que um ponto � fixado, os seus vizinhos passam
			#a ter como vetor de check o vetor de direcionamento do pt_medio relativo a este ponto fixo.
			
			self.check_vector = fixo_relativo.dir_pt_linear
		'''	
		
	def update_3(self,iteration_num):
		self.set_point_color()
		
		self.alfa = self.compute_alfa(iteration_num)
		self.beta = self.compute_beta(iteration_num)
		self.gama = self.compute_gama()		
		self.delta = self.compute_delta()
		
		
	def set_as_neighbors(self,point_vector):
		if self.quina:
			self.closest_characteristic = 0
			self.closest_fixed = 0
			
			for i in range(1,6):
				if abs( point_vector.points[self.index+i].closest_characteristic ) > i:
					point_vector.points[self.index+i].closest_characteristic = -i
					point_vector.points[self.index+i].closest_fixed = -i
					
				if abs( point_vector.points[self.index-i].closest_characteristic ) > i:
					point_vector.points[self.index-i].closest_characteristic = i
					point_vector.points[self.index-i].closest_fixed = i
					
		elif self.sobre_contorno:
			self.closest_fixed = 0
			
			for i in range(1,6):
				if abs( point_vector.points[self.index+i].closest_fixed ) > i:
					point_vector.points[self.index+i].closest_fixed = -i
					
				if abs( point_vector.points[self.index-i].closest_fixed ) > i:
					point_vector.points[self.index-i].closest_fixed = i		
									
	def set_as_border(self,point_vector):
		if point_vector.points[self.index+1].closest_fixed == 0 and point_vector.points[self.index-1].closest_fixed == 0:
			self.sobre_contorno_borda = True
			self.sobre_contorno = False
		else:
			self.sobre_contorno_borda = False
			
	def set_as_characteristic(self,color_fn):	
		
		if self.quina:
			self.sobre_contorno = True
			return		
		
		if self.sobre_contorno or self.sobre_contorno_borda:
			for i in range(-1,2,1):
				for j in range(-1,2,1):
					if j==0 and i==0:
						continue
					
					v = Point( self.x + i, self.y + j,0 )
					if PointFunctions.calcular_quina(v,color_fn):
						self.x = v.x
						self.y = v.y

		
						self.quina = True
						self.sobre_contorno = True
						self.sobre_contorno_borda = False
						return
					
	def neighbor_num(self):
		return abs( self.closest_fixed )
		
	def characteristic_neighbor_num(self):
		return abs( self.closest_characteristic )
				
	def set_point_color(self):
		if self.index % 20  == 0:
			self.color = Colors.BLACK
		elif self.index == 1:
			self.color = Colors.WHITE	
		elif self.quina:
			self.color = Colors.CIAN				
		elif self.sobre_contorno:		
			self.color = Colors.BLUE
		elif self.ponto_intermediario:
			self.color = Colors.YELLOW			
		elif self.sobre_contorno_borda and ( self.neighbor_num() == 1 or self.neighbor_num()==2 ):
			self.color = Colors.ORANGE
		elif self.sobre_contorno_borda and ( self.neighbor_num() == 3 or self.neighbor_num()==4 ):
			self.color = Colors.DARK_ORANGE			
		elif self.sobre_contorno_borda:
			self.color = Colors.DARK_BLUE		
		elif self.neighbor_num() == 1 or self.neighbor_num()==2:
			self.color = Colors.GREEN	
		elif self.neighbor_num() == 3 or self.neighbor_num()==4:			
			self.color = Colors.DARK_GREEN	
		else:
			self.color = Colors.RED	


	def compute_alfa(self,iteration_num):	
		alfa = 1
		
		if self.quina:
			alfa = 0
		elif self.manual_alfa is not None:
			alfa = self.manual_alfa
		elif self.sobre_contorno: 
			alfa = 0.001		
		elif self.neighbor_num()==1 and self.sobre_contorno_borda:
			alfa = 1			
		elif ( self.neighbor_num()==2 or self.neighbor_num()==3 ) and self.sobre_contorno_borda:
			alfa = 0.15			
		elif self.neighbor_num()==4  and self.sobre_contorno_borda:
			alfa = 0.08		
		elif self.sobre_contorno_borda:
			alfa = 0.06   #0.08			
		elif self.neighbor_num()==1:
			alfa = 1.2   #0.65				
		elif self.neighbor_num()==2 or self.neighbor_num()==3:		
			alfa = 0.29						
		elif self.neighbor_num()==4:
			alfa = 0.13		
		elif iteration_num < 100:
			alfa = 1.5
		else:
			alfa = 0.5
			
		return alfa*ALFA_NORMALIZER*10

		
		
	def compute_beta(self,iteration_num):		
		beta = 1
		
		if self.quina:
			beta = 0		
		elif self.manual_beta is not None:
			beta = self.manual_beta		
		elif self.sobre_contorno_borda:
			beta = 0.001			
		elif self.sobre_contorno:
			 beta = 0.02			
		elif self.neighbor_num()==1:
			beta = 0.0005			
		elif ( self.neighbor_num()==2 or self.neighbor_num()==3 ):
			beta = 0.001			
		elif self.neighbor_num()==4:
			beta = 0.002					
		elif iteration_num < 100:
			beta = 0.8
		else:
			beta = 0.005
			
		return beta*BETA_NORMALIZER*10
		
		
	def compute_gama(self):		
		gama = (1,1)
		
		index_x = self.var_cor[0]
		index_y = self.var_cor[1]					
		
		if self.quina:
			gama = (0,0)		
				
		elif self.manual_gama is not None:
			gama = self.manual_gama		
		
		elif (self.sobre_contorno_borda and (self.neighbor_num()==1 or self.neighbor_num()==2) ):
			gama = (index_x,index_y)			
			
		elif self.sobre_contorno or self.sobre_contorno_borda:
			gama = (index_x*0.001,index_y*0.001)

		return ( gama[0]*GAMA_NORMALIZER*10,gama[1]*GAMA_NORMALIZER*10 )
		
	def compute_delta(self):
		#ESTRATEGIA 1

		if self.sobre_contorno or self.sobre_contorno_borda or self.quina:
			return 0		
		
		if self.manual_delta is not None:
			return self.manual_delta
		
		if self.ponto_intermediario:
			if self.manual_delta is not None:
				return float(self.manual_delta)
				
		if self.ponto_intermediario:
			return 1

		if self.neighbor_num()==1:
			return 0.6
			
		if self.neighbor_num()==2 or self.neighbor_num()==3:
			return 0.5
			
		if self.neighbor_num()>=4:
			return 0.4
			
		return 0



class PointVector():
	'''
		Neste objeto vetor, cada unidade, cada componente do vetor � um ponto.
	'''
	
	#Será recebida uma lista normal de pontos. Nesta classe ela será transformada em uma lista
	#especial
	def __init__(self,points=None):
		self.points = PointArraySpecial()
		if points is not None:
			self.points.extend( points )

		self.size = len(self.points)
		
		self.fixeds_points = PointArraySpecial()
		self.fixeds_perc = 0 #Porcentagem de pontos fixo
		self.last_fixeds_perc = 0 #Porcentagem de pontos fixos da iteracao anterior
		
		self.medium_pt = None		
			
	def update(self,color_fn,history,actual_iteration):	
		medium_x,medium_y=0,0
		
		self.last_fixeds_perc = self.fixeds_perc
		self.fixeds_perc = 0
		
		size = len(self.points)
		self.fixeds_points = PointArraySpecial()

		for p in self.points:

			medium_x+= p.x
			medium_y+= p.y
										
			if self.last_fixeds_perc<0.5 and self.medium_pt is not None: #A direcao central so e atualizada ate 50% dos pontos se tornarem fixos
				dir_pt_medio = Point( (self.medium_pt.x - p.x) , (self.medium_pt.y - p.y), -1 )
				
				mod_pt = math.sqrt( pow(dir_pt_medio.x,2) + pow(dir_pt_medio.y,2) )
				dir_pt_medio.x = dir_pt_medio.x/mod_pt
				dir_pt_medio.y = dir_pt_medio.y/mod_pt
				
				p.dir_pt_linear = dir_pt_medio
				
			p.update_1(self,color_fn)						
		
		#Compute medium_point						
		medium_x = medium_x/float(size)
		medium_y = medium_y/float(size)
		
		self.medium_pt = Point(medium_x,medium_y,-1)
		self.medium_pt.color = Colors.PINK										
							
		for p in self.points:			
			p.update_2(self,color_fn)	
			
			if p.sobre_contorno:
				self.fixeds_perc+=1
				self.fixeds_points.append(p)
				
		self.fixeds_perc = float( self.fixeds_perc ) / self.size	
		self.fixeds_perc_var = abs( self.fixeds_perc - self.last_fixeds_perc )	
		

		#Setando Intermdiarios
		len_fixeds = len(self.fixeds_points)
		if len_fixeds > 0:			
			for i in range(0,len_fixeds-1 ):
				fixed_point_1 = self.fixeds_points[i]
				fixed_point_2 = self.fixeds_points[i+1]
				
				p1_index = self.fixeds_points[i].index
				p2_index = self.fixeds_points[i+1].index
				
				diff_fixeds = (p2_index - p1_index)%self.size
				
				if diff_fixeds<=5:
					len_intermediated_pt = diff_fixeds
				else:
					len_intermediated_pt = 5 + diff_fixeds/6
					
				perp1_initial_index = (diff_fixeds - len_intermediated_pt)/2 + p1_index
				perp1_end_index = perp1_initial_index + len_intermediated_pt
								
				perps = self.__get_perps__( fixed_point_1,fixed_point_2 )				
				
				for j in range(perp1_initial_index,perp1_end_index):
					pi = self.points[j]
					
					if pi.sobre_contorno or pi.quina:
						continue
					
					pi.ponto_intermediario = True																				
					pi.dir_pt_linear = perps[0]


			fixed_point_1 = self.fixeds_points[0]
			fixed_point_2 = self.fixeds_points[-1]
			
			p1_index = self.fixeds_points[0].index
			p2_index = self.fixeds_points[-1].index					
			
			diff_fixeds = len(self.points) - (p2_index-p1_index)
			
			if diff_fixeds<=5:
				len_intermediated_pt = diff_fixeds
			else:
				len_intermediated_pt = 5 + diff_fixeds/6				
			
			perp2_initial_index = (diff_fixeds - len_intermediated_pt)/2 + p2_index
			perp2_end_index = perp2_initial_index + len_intermediated_pt
			
			perps = self.__get_perps__(fixed_point_1,fixed_point_2)
	
			for j in range(perp2_initial_index,perp2_end_index):
				index = (j)%len(self.points)
				pi = self.points[index]
								
				if pi.sobre_contorno or pi.quina:
					continue
				
				pi.ponto_intermediario = True																				
				pi.dir_pt_linear = perps[1]					

					
		for p in self.points:
			p.update_3(actual_iteration.iteration)
			
			info = InfoDebug()
			info.ponto_index = p.index
			
			info.alfa = p.alfa
			info.beta = p.beta
			info.gama = p.gama
			
			info.ponto_pair = ( p.x,p.y )
			
			actual_iteration.add_info(info)
			
		return self.medium_pt
		
	def __get_perps__(self,pt_1,pt_2):
		rect_dir = Point( (pt_2.x - pt_1.x),(pt_2.y - pt_1.y),0 )
			
		normal = rect_dir.normal()
		if normal>0:
			rect_dir.x = rect_dir.x/normal
			rect_dir.y = rect_dir.y/normal
		
		perp = Point( -rect_dir.y,rect_dir.x,0 )
		
		vector_product = rect_dir.x*perp.y - rect_dir.y*perp.x
		
		if vector_product > 0:
			perp1 = perp
			perp2 = perp.copy()
			
			perp2.x = perp2.x*-1
			perp2.y = perp2.y*-1
		else:
			perp2 = perp
			perp1 = perp.copy()
			
			perp1.x = perp1.x*-1
			perp1.y = perp1.y*-1	

		return (perp1,perp2)
				
	def normal(self):
		normal = 0
		for p in self.points:
			normal+= pow( p.x,2 ) + pow( p.y,2 )
		
		return pow( normal,0.5 )
		
	def escalar_product(self,escalar):
		result = self.copy()
		
		for i in range( 0,len(result.points) ):	
			result.points[i].x *= escalar			
			result.points[i].y *= escalar
			
		return result
		
	def escalar_sum(self,escalar):
		result = self.copy()
		
		for pt in result.points:
			pt.x += escalar
			pt.y += escalar
			
		return result	
		
	def dot_product(self,vetor):
		sum = 0
		
		for i in range(0,self.size):
			sum+= ( self.points[i].x * vetor.points[i].x ) + ( self.points[i].y * vetor.points[i].y )
			
		return sum
		
	def sum_vector(self,vector,atualizar_posicoes=False):
		result = self.copy()

		for i in range(0, result.size):
			result.points[i].x = self.points[i].x + vector.points[i].x
			result.points[i].y = self.points[i].y + vector.points[i].y
		
		return result		
		
	def add_point(self, point):
		self.points.append( point )
		self.size+=1
		
	def copy(self):
		_copy = PointVector()
		
		for p in self.points:		
			_copy.add_point( p.copy() )

		_copy.size = self.size
		_copy.medium_pt = self.medium_pt
		
		_copy.fixeds_perc = self.fixeds_perc 
		_copy.last_fixeds_perc = self.last_fixeds_perc		
		
		return _copy
		
	def __str__(self):
		buffer = ""
		for p in self.points:
			buffer+= " (" + str(p.x) + "," + str(p.y) + ") "
			
		return buffer
		
		
		
class PointArraySpecial(list):
	
	'''
		Dessa forma, se o indice for maior do que o tamanho da lista, ele começa do início e 
		o comportamento para o índice negativos continua o mesmo
	'''
	
	def __getitem__(self,index):
		relative_point = ( index ) % len( self )
		return super(PointArraySpecial,self).__getitem__(relative_point)
	
		
class Debug():
	
	def __init__(self):
		self.iteracoes = []
		
	def add_info(self,iteracao):
		self.iteracoes.append(iteracao)
		
	def imprimir(self):
		for it in self.iteracoes:
			print "######### ITERACAO ",it.iteration," ##########\n\n"
			
			print "Lambda :\t",it.lbda,"\n\n"
			print "Direcao :\t",it.direcao,"\n\n"
			print "XK :\t",it.xk,"\n\n"
			print "Funcional :\t",it.funcional,"\n\n"
			
			
			for info in it.infos:
				print "*** PONTO ",info.ponto_index," ",info.ponto_pair," *** \n\n"
				
				print "Continuidade :\t",info.comp_continuidade,"\n"
				print "Suavidade :\t",info.comp_suavidade,"\n"
				print "Cor :\t",info.comp_cor,"\n\n"
				
				print "Grad Continuidade :\t",info.grad_comp_continuidade,"\n"
				print "Grad Suavidade :\t",info.grad_comp_suavidade,"\n"
				print "Grad Cor :\t",info.grad_comp_cor,"\n\n"
				
				print "Alfa :\t",info.alfa,"\n"
				print "Beta :\t",info.beta,"\n"
				print "Gama :\t",info.gama,"\n\n\n"							
					
class InfoIteration():

	def __init__(self,iteration):
		self.iteration = iteration
		self.direcao = None
		self.lbda = None
		self.xk = None
		self.funcional = None
		
		self.infos = []
		
	def add_info(self,info):
		self.infos.append(info)
		
	def set_direcao(self,direcao):
		self.direcao = direcao
	
	def set_lambda(self,lbda):
		self.lbda = lbda
		
	def set_funcional(self,func):
		self.funcional = func
		
	def set_xk(self,xk):
		self.xk = xk
	
class InfoDebug():

	def __init__(self):
		self.ponto_index = None
		
		self.comp_continuidade = None
		self.comp_suavidade = None
		self.comp_cor = None
		
		self.grad_comp_continuidade = None
		self.grad_comp_suavidade = None
		self.grad_comp_cor = None
		
		self.alfa = None
		self.beta = None
		self.gama = None
		
		self.lbda = None
		
		self.ponto_pair = None
		
class ImageDebug():
	instance = None
	
	def __init__(self,img):
		if ImageDebug.instance is None:
			self.surf = pygame.Surface( (2000,2000 ) )
			self.surf = self.surf.convert()
			
			self.img = img
			
			self.count = 0
			self.it = 1
			
			ImageDebug.instance = self
			
	@staticmethod
	def get_instance(img):
		if ImageDebug.instance is None:
			ImageDebug.instance = ImageDebug(img)
			
		ImageDebug.instance.img = img
		
		return ImageDebug.instance
		
	def print_image(self):
		rect = pygame.Rect(0,0,500,500)
		
		rect.left = ( (self.count)%4 ) * 500
		rect.top = ( (self.count) / 4) * 500
				
		self.surf.blit( self.img,rect )
		
		textRect = pygame.Rect( (rect.left+150,rect.top+300), (200,30) )
		textColor = (255,255,255)
		font = pygame.font.Font(None,24)
		text = font.render( "Iteracao " + str(self.it), 1, textColor,Colors.BLACK )
		
		self.surf.blit( text,textRect )
		
		self.it += 1
		self.count += 1
		self.count %= 16
		